﻿namespace TheCodeWerks.Advertising
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Threading;

	// Static Fields + Properties
	public partial class AdvertisingControl : UserControl
	{
		private static Dictionary<AdProvider, IAdvertisingProvider> availableProviders = new Dictionary<AdProvider, IAdvertisingProvider>();
		public static AdvertisingConfiguration Config { get; set; }
	}

	// Static Methods
	public partial class AdvertisingControl : UserControl
	{
		public static void SetProvider<T>() where T : IAdvertisingProvider, new()
		{
			if (Config == null) throw new ArgumentNullException("Config must be set before providers can be added");

			var obj = new T();
			obj.Config = Config;
			availableProviders.Add(obj.Provider, obj);
		}
	}

	// Instance Fields + Properties
	public partial class AdvertisingControl : UserControl
	{
		private Dictionary<AdProvider, FrameworkElement> adcontrols = new Dictionary<AdProvider, FrameworkElement>(availableProviders.Count);
		private List<AdUnit> availableUnits;
		private AdEvents events;
		private bool initialized = false;
		private AdPlacement placement;
		private AdUnit selectedUnit;
		private IWeightedSelector selector;
		private DispatcherTimer timer = new DispatcherTimer();
		private Grid visibleFrame;
		private Grid hiddenFrame;

		public int PlacementKey { get; set; }
	}

	// Instance Methods
	public partial class AdvertisingControl : UserControl
	{
		public AdvertisingControl()
		{
			InitializeComponent();

			visibleFrame = AdFrame2;
			hiddenFrame = AdFrame1;

			events = new AdEvents()
			{
				AdReady = AdReady,
				NotFilled = NotFilled
			};
		}

		public void Run() { Run(null); }

		public void Run(IWeightedSelector selector)
		{
			if (initialized) return;

			if (availableProviders.Count == 0) throw new Exception("You must add providers before ads are displayed.");

			if (!Config.Placements.ContainsKey(PlacementKey))
				throw new KeyNotFoundException("The specified ad placement key was not found in the config file.");

			this.selector = selector ?? new PercentageSelector();
			placement = Config.Placements[PlacementKey];

			foreach (var provider in (from unit in placement.Units select unit.Provider).Distinct())
			{
				var control = availableProviders[provider].CreateControl();
				control.Tag = events;

				adcontrols.Add(provider, control);
			}

			availableUnits = new List<AdUnit>(placement.Units.Count);
			timer.Interval = TimeSpan.FromSeconds(placement.Interval);
			timer.Tick += (source, args) => Rotate();

			Rotate();
		}

		private void Rotate()
		{
			timer.Stop();
			availableUnits.Clear();

			placement.Units.ForEach(item => availableUnits.Add(item));
			selectedUnit = selector.Select(availableUnits);

			RequestNext();
		}

		private void RequestNext()
		{
			var provider = availableProviders[selectedUnit.Provider];
			var control = adcontrols[selectedUnit.Provider];

			provider.ConfigureControl(control, selectedUnit);

			if (control.Parent == null)
			{
				hiddenFrame.Children.Add(control);
			}

			provider.RequestNext(control);
		}

		private void AdReady()
		{
			var provider = availableProviders[selectedUnit.Provider];
			var control = adcontrols[selectedUnit.Provider];

			if (!visibleFrame.Children.Contains(control))
			{
				hiddenFrame.Visibility = Visibility.Visible;
				visibleFrame.Visibility = Visibility.Collapsed;
				visibleFrame.Children.Clear();

				if (hiddenFrame == AdFrame1)
				{
					visibleFrame = AdFrame1;
					hiddenFrame = AdFrame2;
				}
				else
				{
					visibleFrame = AdFrame2;
					hiddenFrame = AdFrame1;
				}
			}

			Visibility = Visibility.Visible;

			timer.Start();
		}

		private void NotFilled()
		{
			availableUnits.Remove(selectedUnit);

			if (availableUnits.Count == 0)
			{
				timer.Start();
				return;
			}

			selectedUnit = selector.Select(availableUnits);
			RequestNext();
		}
	}
}
